# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

from __future__ import annotations

import hashlib
import pathlib
import re
import unicodedata
from typing import Final, Optional, TypeAlias

# A path that can refer to files on a remote platform with potentially
# a different Path flavour (e.g. Win vs Posix).
AnyPath: TypeAlias = pathlib.PurePath
AnyPosixPath: TypeAlias = pathlib.PurePosixPath
AnyWindowsPath: TypeAlias = pathlib.PureWindowsPath

AnyPathLike: TypeAlias = str | AnyPath

# A path that only ever refers to files on the local host / runner platform.
# Not that Path inherits from PurePath, and thus we can use a LocalPath in
# all places a RemotePath is expected.
LocalPath: TypeAlias = pathlib.Path
LocalPosixPath: TypeAlias = pathlib.PosixPath

LocalPathLike: TypeAlias = str | LocalPath

MAX_PART_LEN: Final[int] = 255

_UNSAFE_FILENAME_CHARS_RE: Final[re.Pattern[str]] = re.compile(
    r"[^a-zA-Z0-9+\-_.]")


def safe_filename(name: str, strict_len: bool = False) -> str:
  normalized_name = unicodedata.normalize("NFKD", name)
  ascii_name = normalized_name.encode("ascii", "ignore").decode("ascii")
  safe_name: str = _UNSAFE_FILENAME_CHARS_RE.sub("_", ascii_name)
  if strict_len and len(safe_name) > MAX_PART_LEN:
    raise ValueError(f"Too long file name: {repr(safe_name)}")
  return safe_name[:MAX_PART_LEN]


def try_resolve_existing_path(value: str) -> Optional[LocalPath]:
  if not value:
    return None
  maybe_path = LocalPath(value)
  if maybe_path.exists():
    return maybe_path
  maybe_path = maybe_path.expanduser()
  if maybe_path.exists():
    return maybe_path
  return None


def check_hash(file_path: LocalPath, file_hash: str) -> bool:
  if not file_path.exists():
    return False
  sha1 = hashlib.sha1(usedforsecurity=False)
  sha1.update(file_path.read_bytes())
  return sha1.hexdigest() == file_hash
